/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_httpd.htm
 *
 * Zhiqim Httpd is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.httpd.context;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.SocketException;
import java.rmi.RemoteException;
import java.sql.SQLException;
import java.util.List;

import org.zhiqim.httpd.HttpException;
import org.zhiqim.httpd.HttpExecutor;
import org.zhiqim.httpd.HttpRequest;
import org.zhiqim.httpd.HttpResponse;
import org.zhiqim.httpd.context.annotation.AnLogNot;
import org.zhiqim.httpd.context.config.ZAction;
import org.zhiqim.httpd.context.core.Action;
import org.zhiqim.httpd.context.core.Context;
import org.zhiqim.httpd.context.core.Interceptor;
import org.zhiqim.httpd.context.extend.DispatchAction;
import org.zhiqim.httpd.context.extend.GetPostAction;
import org.zhiqim.httpd.context.extend.StdDispatchAction;
import org.zhiqim.httpd.context.extend.StdSwitchAction;
import org.zhiqim.httpd.context.extend.ValidateAction;
import org.zhiqim.httpd.context.extend.ViewExecAction;
import org.zhiqim.httpd.context.returns.ReturnResult;
import org.zhiqim.httpd.context.returns.ReturnView;
import org.zhiqim.kernel.annotation.AnTransaction;
import org.zhiqim.kernel.transaction.Transaction;
import org.zhiqim.kernel.transaction.TransactionManager;
import org.zhiqim.kernel.util.Arrays;
import org.zhiqim.kernel.util.Classes;
import org.zhiqim.kernel.util.Lists;
import org.zhiqim.kernel.util.Validates;
import org.zhiqim.zml.ZmlEngine;
import org.zhiqim.zml.ZmlLoader;

/**
 * Action处理器
 * 1.除*.zml和系统的/service/ajax等之外的path都在该处理器
 * 2.支持配置Action，实现MVC模式优先Action的操作
 * 3.支持配置通配符访问Action和对应的方法的配置,如<action path="abc*.htm" class="org.zhiqim.action.Abc" method="${1}"/>
 *
 * @version v1.0.0 @author zouzhigang 2014-3-21 新建与整理
 */
public class ZmlExecutor implements HttpExecutor, ZmlContextConstants
{
    private List<String> patternList;
    private ReturnView returnView;
    private ReturnResult returnResult;
    
    public ZmlExecutor(String patterns)
    {
        this.patternList = Lists.toStringList(patterns);
        
        this.returnView = new ReturnView();
        this.returnResult = new ReturnResult();
    }
    
    /**********************************************************************************************/
    //implement HttpHandler & HttpExecutor
    /**********************************************************************************************/
    
    @Override
    public boolean isMatch(String pathInContext)
    {//默认全匹配，在handle中处理未匹配的
        return true;
    }
    
    @Override
    public void handle(HttpRequest request, HttpResponse response) throws HttpException, IOException
    {
        Context context = (Context)request.getContext();
        String path = request.getPathInContext();
        
        ZAction zAction = context.getAction(path);
        if (zAction != null)
        {//匹配到Action，查出Action处理
            handleAction(request, response, zAction);
        }
        else if (isMatchPattern(path))
        {//匹配到ZML文件，则访问ZML文件
            handleZML(request, response);
        }
        else
        {//未找到配置，回调资源处理器
            context.handleResource(request, response);
        }
    }
    
    private boolean isMatchPattern(String pathInContext)
    {//判断是否配置模式
        for (String pattern : patternList)
        {
            if (Validates.isMatch(pathInContext, pattern))
                return true;
        }
        return false;
    }
    
    /**********************************************************************************************/
    //handleZML & handleAction
    /**********************************************************************************************/

    /** 处理ZML文件 */
    private void handleZML(HttpRequest request, HttpResponse response) throws HttpException, IOException
    {
        Context context = (Context)request.getContext();
        String path = request.getPathInContext();
        ZmlEngine engine = context.getZmlEngine();
        if (engine == null)
        {//不支持ZML引擎
            context.getNotFoundHandler().handle(request, response);
            return;
        }
        
        ZmlLoader loader = engine.getZmlLoader(path);
        if (loader == null)
        {//不存在ZML文件
            context.getNotFoundHandler().handle(request, response);
            return;
        }
        
        if (request.isMethodHead())
        {//HEAD
            response.sendHeader(_200_OK_);
            return;
        }
        
        //业务处理开始，初始化响应，包括指定UTF-8编码，和不缓存数据
        request.setResponseEncodingUTF8();
        request.setResponseNoCache();
        
        //指定请求为视图地址并处理
        returnView.process(request, path, null);
        
        if (request.isForceReturn() || !request.isResponseSuccess())
        {//强制跳转的，和后置检查视图中的<#interceptor>，可能的跳转
            returnResult.process(request);
        }
    }
    
    /** 处理Action */
    private void handleAction(HttpRequest request, HttpResponse response, ZAction zAction) throws HttpException, IOException
    {
        //上下文环境和引导类
        Context context = (Context)request.getContext();
        ZmlBootstrap bootstrap = context.getBootstrap();
            
        //第二步，对HEAD方法请求，直接返回成功，表示该Path存在
        if (request.isMethodHead())
        {
            bootstrap.log(request, "HEAD请求");
            response.sendHeader(_200_OK_);
            return;
        }
        
        //业务处理开始，初始化响应，包括指定UTF-8编码，和不缓存数据
        request.setResponseEncodingUTF8();
        request.setResponseNoCache();
        request.setAttribute(_HTTP_REQUEST_PARAM_MATCH_, zAction.getMatchList());
        
        //第三步，找到对应的Action
        Action action = context.getAction(zAction);
        
        //组装ActionForward
        Throwable exception = null;String info = null;Transaction tx = null;
        request.setResponseConfig(zAction.getView(), zAction.getInclude(), zAction.getForward(), zAction.getRedirect(), zAction.getMethod(), zAction.getSuccess());

        try
        {
            request.setStepInterceptor();
            
            //第四步，先处理Interceptor的process操作
            List<Interceptor> interceptorList = context.getInterceptorList(zAction.getInterceptor());
            for (Interceptor interceptor : interceptorList)
            {
                if (interceptor == null)
                {//拦截器不存在
                    response.sendError(_621_INTERCEPTOR_NOT_EXIST_);
                    return;
                }
                
                interceptor.intercept(request);
                if (response.isCommitted()) 
                {//允许被拦截器提交终止
                    info = "拦截器已提交";
                    return;
                }
                
                if (!request.isResponseSuccess())
                {//拦截器已拦截
                    info = "拦截器已拦截";
                    return;
                }
            }
            
            //第五步，再处理Action的execute操作
            tx = getTransaction(request, zAction, action);

            request.setStepAction();
            action.execute(request);
            
            if (tx != null)
            {//有事务则提交，异常在捕捉中处理
                tx.commit();
            }
        }
        catch (ZmlException | SQLException | RemoteException | RuntimeException e) 
        {//在 Interceptor/Action execute的过程中遇到的错误和异常
            exception = e;
            
            //业务异常显示
            String message = Validates.isEmpty(e.getMessage())?e.getClass().getName():e.getMessage();
            request.returnHistory(message);
            //request.setAlertMsg(message); TODO 是否不返回上一页？
            
            if (tx != null)
            {//如果有事务则回滚
                try{tx.rollback();}catch (Exception ex){request.getLog().error("事务回滚异常", ex);}
            }
        }
        catch (SocketException e)
        {//在处理时浏览器端取消、停止或关闭浏览器
            exception = e;
            
            if (tx != null)
            {//如果有事务则回滚
                try{tx.rollback();}catch (Exception ex){request.getLog().error("事务回滚异常", ex);}
            }
        }
        catch (Throwable e) 
        {//错误等其他异常
            if (e instanceof InvocationTargetException)
            {//反射调用异常
                e = ((InvocationTargetException) e).getTargetException();
            }
            
            exception = e;
            
            // 未知异常都返回上一页
            String message = Validates.isEmpty(e.getMessage())?e.getClass().getName():e.getMessage();
            request.returnHistory(message);
            
            if (tx != null)
            {//如果有事务则回滚
                try{tx.rollback();}catch (Exception ex){request.getLog().error("事务回滚异常", ex);}
            }
        }
        finally
        {
            if (tx != null)
            {//有事务则关闭
                try{tx.close();}catch (Exception ex){request.getLog().error("事务关闭异常", ex);}
            }
            
            if (exception instanceof SocketException)
            {//SocketException不能回消息了
                bootstrap.log(request, "浏览器端取消、停止或已关闭");
                throw (SocketException)exception;
            }
            
            boolean isPrintLog = !hasAnLogNot(action, zAction);
            
            if (response.isCommitted())
            {//已提交只打日志不再处理
                if (!request.hasForward() && isPrintLog)//重定向只打印一次日志
                    bootstrap.log(request, info, exception);
                return;
            }
            
            if (request.isForceReturn())
            {//优先检查需要强制转向情况
                
                String message = request.getAlertMsg();
                if (Validates.isNotEmpty(message))
                {//如果不为空，提示时增加成功和失败说明
                    info = (request.isResponseSuccess()?"成功":"失败") + "信息:" + message;
                }
                
                if (isPrintLog)
                    bootstrap.log(request, info, exception);
                returnResult.process(request);
                return;
            }
            
            if (request.isReturn())
            {//再判断是否需要转向到页面
                
                if (Validates.isNotEmpty(request.getForward(_UTF_8_)))
                {//内部转向
                    request.forwardTo(request.getForward(_UTF_8_));
                }
                else if (Validates.isNotEmpty(request.getView()))
                {//加载视图
                    returnView.process(request);
                    
                    if (!request.isResponseSuccess())
                    {//后置检查视图中的<#interceptor>，可能的跳转
                        returnResult.process(request);
                    }
                    
                    if (isPrintLog)
                    {//没有配置AnLogNot的打印日志 TODO 没有完全覆盖
                        bootstrap.log(request, info, exception);
                    }
                }
                else
                {//重定向，日志要先打
                    if (request.isMethodPost() && isPrintLog)
                        bootstrap.log(request, info, exception);
                    
                    String redirect = request.getRedirect(_UTF_8_);
                    response.sendRedirect(context.getRootPath(redirect));    
                }
                
                return;
            }
            
            if (isPrintLog)
            {//没有配置AnLogNot的打印日志 TODO 没有完全覆盖
                bootstrap.log(request, info, exception);
            }
        }
    }
    
    /**********************************************************************************************/
    //transaction
    /**********************************************************************************************/
    
    /** 通过配置和事务注释判断是否有事务 */
    private Transaction getTransaction(HttpRequest request, ZAction fAction, Action action) throws Exception
    {
        //1.优先判断是否配置transaction，=true表示所有
        if (Validates.isNotEmpty(fAction.getTransaction()))
        {
            String transaction = fAction.getTransaction();
            if (_TRUE_.equals(transaction))
                return TransactionManager.beginTransaction();
            else
                return TransactionManager.beginTransaction(Arrays.toStringArray(transaction));
        }
        
        //2.其次判断方法里有没有配置事务注解
        String fMethod = fAction.getMethod();
        if (Validates.isEmpty(fMethod))
        {//取默认方法
            fMethod = getActionDefaultMethod(request, action);
        }
        
        if (Validates.isNotEmpty(fAction.getMethod()))
        {
            Method method = Classes.getMethod(action.getClass(), fMethod, HttpRequest.class);
            if (method != null)
            {
                AnTransaction anTx = method.getAnnotation(AnTransaction.class);
                if (anTx != null)
                    return TransactionManager.beginTransaction(anTx.value());
            }
        }
        
        //3.最后判断类有没有统一的配置事务注解
        AnTransaction anTx = action.getClass().getAnnotation(AnTransaction.class);
        if (anTx != null)
        {//1.类中有定义事务注解
            return TransactionManager.beginTransaction(anTx.value());
        }
        
        return null;
    }
    
    /** 获取扩展Action */
    private String getActionDefaultMethod(HttpRequest request, Action action)
    {
        if (action instanceof ValidateAction)
            return "perform";
        else if (action instanceof ViewExecAction)
            return "view";
        else if (action instanceof StdSwitchAction)
            return "list";
        else if (action instanceof StdDispatchAction || action instanceof DispatchAction)
            return request.getParameter(ZC_METHOD);
        else if (action instanceof GetPostAction)
        {
            if (request.isMethodGet())
                return "doGet";
            else if (request.isMethodPost())
                return "doPost";
            else
                return null;
        }
        
        return "execute";
    }
    
    /** 是否配置了不打印日志的注解 */
    private boolean hasAnLogNot(Action action, ZAction zAction)
    {
        AnLogNot logNot = action.getClass().getAnnotation(AnLogNot.class);
        if (logNot != null)
            return true;
        
        String method = Validates.isNotEmpty(zAction.getMethod())?zAction.getMethod():"execute";
        Method curMethod = Classes.getMethod(action.getClass(), method, HttpRequest.class);
        if (curMethod != null)
        {
            logNot = curMethod.getAnnotation(AnLogNot.class);
        }
        
        return logNot != null;
    }
}
